package ch12.completableFutures;

import java.awt.image.BufferedImage;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;

public class CompletableFutureDemo {
    private static final Pattern IMG_PATTERN = Pattern.compile(
        "<img\s+[^>]*src\s*=\s*(?:\"([^\"]+)\"|'([^']+)'|([^\s>]+))[^>]*>",
        Pattern.CASE_INSENSITIVE);

    private ExecutorService executor = Executors.newCachedThreadPool();
    private URI uriToProcess;

    public CompletableFuture<String> readPage(URI uri) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                var url = uri.toURL();
                var contents = new String(
                    url.openStream().readAllBytes(), StandardCharsets.UTF_8);
                System.out.println("Read page from " + url);
                return contents;
            }
            catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }, executor);
    }

    public List<URI> getImageURIs(String webpage) {
        var result = new ArrayList<URI>();
        Matcher matcher = IMG_PATTERN.matcher(webpage);
        while (matcher.find()) {
            String src = matcher.group(1);
            if (src == null) {
                src = matcher.group(2);
            }
            if (src == null) {
                src = matcher.group(3);
            }
            if (src == null) {
                continue;
            }
            var imgURI = uriToProcess.resolve(src);
            result.add(imgURI);
        }
        System.out.println("Found URIs: " + result);
        return result;
    }

    public CompletableFuture<List<BufferedImage>> getImages(List<URI> uris) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                var result = new ArrayList<BufferedImage>();
                for (URI uri : uris) {
                    result.add(ImageIO.read(uri.toURL()));
                }
                return result;
            }
            catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }, executor);
    }

    public void saveImages(List<BufferedImage> images) {
        System.out.println("Saving " + images.size() + " images");

        try {
            for (int i = 0; i < images.size(); i++) {
                String filename = CompletableFutureDemo.class.getResource("")
                                    .getPath() +  "/image" + (i + 1) + ".png";
                ImageIO.write(images.get(i), "PNG", new File(filename));
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
        executor.shutdown();
    }

    public void run(URI uri) throws IOException, InterruptedException {
        uriToProcess = uri;
        CompletableFuture.completedFuture(uri)
            .thenComposeAsync(this::readPage, executor)
            .thenApply(this::getImageURIs)
            .thenCompose(this::getImages)
            .thenAccept(this::saveImages);
    }

    public static void main(String[] args) throws IOException,
            InterruptedException, URISyntaxException {
        new CompletableFutureDemo().run(new URI("https://www.baidu.com/"));
    }
}
